package thulearningcore.util;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

// This class downloads a file from a URL.

public class TLDownloader extends Observable implements Runnable {

	// Max size of download buffer.
	private static final int MAX_BUFFER_SIZE = 65536;

	// These are the state names.
	public static final String STATES[] = { "Downloading", "Paused",
			"Complete", "Cancelled", "Error" };

	// These are the status codes.
	public static final int NOT_START = -1;
	public static final int DOWNLOADING = 0;
	public static final int PAUSED = 1;
	public static final int COMPLETE = 2;
	public static final int CANCELLED = 3;
	public static final int ERROR = 4;

	private URL url; // download URL
	private int size; // size of download in bytes
	private int downloaded; // number of bytes downloaded
	private int state; // current status of download

	private String path;
	private String fileName;

	private String fullName;
	private String MIMEType;

	// Constructor for Download.
	public TLDownloader(URL url, String path, String filename) {
		this.url = url;
		size = -1;
		downloaded = 0;
		state = NOT_START;

		this.path = path;
		this.fileName = filename;
		this.fullName = null;
		this.MIMEType = null;

		// Begin the download.
		// download();
	}

	// Get this download's URL.
	public String getUrl() {
		return url.toString();
	}

	// Get this download's size.
	public int getSize() {
		return size;
	}

	// Get this download's progress.
	public float getProgress() {
		return ((float) downloaded / (size > 0 ? size : 1)) * 100;
	}

	// Get this download's status.
	public int getState() {
		return state;
	}

	// Pause this download.
	protected void pause() {
		state = PAUSED;
		stateChanged();
	}

	// Resume this download.
	protected void resume() {
		state = DOWNLOADING;
		stateChanged();
		download();
	}

	// Cancel this download.
	public void cancel() {
		state = CANCELLED;
		stateChanged();
	}

	// Mark this download as having an error.
	private void error() {
		state = ERROR;
		stateChanged();
	}

	// Start or resume downloading.
	public void download() {
		state = DOWNLOADING;
		stateChanged();
		Thread thread = new Thread(this);
		thread.start();
	}

	public String getFullName() {
		return this.fullName;
	}

	public String getMIMEType() {
		return MIMEType;
	}

	// Get file name portion of URL.
	// private String getFileName(URL url) {
	// String fileName = url.getFile();
	// return fileName.substring(fileName.lastIndexOf('/') + 1);
	// }

	// Download file.
	public void run() {
		RandomAccessFile file = null;
		BufferedInputStream stream = null;

		try {
			// Open connection to URL.
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();

			// Specify what portion of file to download.
			connection.setRequestProperty("Range", "bytes=" + downloaded + "-");

			// Add Cookie Info
			connection.addRequestProperty("Cookie", TLCookieManager
					.getInstance().getCookies());
			TLCookieManager.getInstance()
					.addCookieFromURLConnection(connection);

			// Connect to server.
			connection.connect();

			// Make sure response code is in the 200 range.
			if (connection.getResponseCode() / 100 != 2) {
				error();
			}

			// Check for valid content length.
			int contentLength = connection.getContentLength();
			if (contentLength < 1) {
				error();
			}

			// Set the size for this download if it hasn't been already set.
			if (size == -1) {
				size = contentLength;
				stateChanged();
			}

			// Get File's Full Name And MIME Type Info
			String remoteFile = connection
					.getHeaderField("Content-Disposition");
			Matcher m = Pattern.compile("filename=\"[^.]*.([^\"]*)").matcher(
					remoteFile);

			if (!m.find()) {
				error();
			}
			this.fullName = this.fileName + "." + m.group(1);
			this.MIMEType = connection.getContentType();

			// Open file and seek to the end of it.
			file = new RandomAccessFile(path + fullName, "rw");
			file.seek(downloaded);

			stream = new BufferedInputStream(connection.getInputStream(),
					MAX_BUFFER_SIZE);
			byte buffer[] = new byte[MAX_BUFFER_SIZE];
			while (state == DOWNLOADING) {

				// Read from server into buffer.
				int read;
				if ((read = stream.read(buffer)) == -1)
					break;

				// Write buffer to file.
				file.write(buffer, 0, read);
				downloaded += read;
				stateChanged();
			}

			// Change state to complete if this point was reached because
			// downloading has finished.
			if (state == DOWNLOADING) {
				state = COMPLETE;
				stateChanged();
			} else if (state == CANCELLED) {
				file.close();
				file = null;
				new File(path + fullName).delete();
			}
		} catch (Exception e) {
			error();
		} finally {
			// Close file.
			if (file != null) {
				try {
					file.close();
				} catch (Exception e) {
				}
			}

			// Close connection to server.
			if (stream != null) {
				try {
					stream.close();
				} catch (Exception e) {
				}
			}
		}
	}

	// Notify observers that this download's state has changed.
	private void stateChanged() {
		setChanged();
		notifyObservers();
	}

}
